{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# <center>Прогноз оттока клиентов из телеком-компании</center>\n",
    "<center> Автор: Роман Сарычев"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from __future__ import (absolute_import, division, print_function, unicode_literals)\n",
    "import warnings\n",
    "warnings.simplefilter('ignore')\n",
    "\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "%pylab inline\n",
    "import seaborn as sns\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "from sklearn import preprocessing\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "from sklearn.ensemble import GradientBoostingClassifier\n",
    "from sklearn.svm import SVC\n",
    "\n",
    "from sklearn.grid_search import GridSearchCV\n",
    "from sklearn.cross_validation import StratifiedKFold\n",
    "from sklearn.cross_validation import train_test_split\n",
    "from sklearn.learning_curve import validation_curve\n",
    "from sklearn.learning_curve import learning_curve\n",
    "from sklearn.metrics import f1_score\n",
    "from sklearn.metrics import accuracy_score"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**[Данные](https://bigml.com/user/francisco/gallery/dataset/5163ad540c0b5e5b22000383) по оттоку клиентов в телком-компании.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Чтение файла\n",
    "df = pd.read_csv('../../data/telecom_churn.csv')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <center>Описание набора данных и признаков</center>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Проверка, что файл прочитался нормально\n",
    "df.head().T"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Каждая строка представляет собой одного клиента - это **объект** исследования.  \n",
    "Столбцы - **признаки** объекта.\n",
    "\n",
    "\n",
    "Описание признаков объекта:  \n",
    "**State** - Буквенный код штата, номинальный признак  \n",
    "**Account length** - Общее время, в течение которого клиент обслуживается компанией, количественный признак  \n",
    "**Area code** - Префикс номера телефона, количественный признак   \n",
    "**International plan** - Международный роуминг, бинарный признак (подключен/не подключен)  \n",
    "**Voice mail plan** - Голосовая почта, бинарный признак (подключена/не подключена)  \n",
    "**Number vmail messages** - Количество голосовых сообщений, количественный признак  \n",
    "**Total day minutes** - Общая длительность разговоров днем, количественный признак  \n",
    "**Total day calls** - Общее количество звонков днем, количественный признак  \n",
    "**Total day charge** - Общая сумма оплаты за услуги днем, количественный признак  \n",
    "**Total eve minutes** - Общая длительность разговоров вечером, количественный признак  \n",
    "**Total eve calls** - Общее количество звонков вечером, количественный признак  \n",
    "**Total eve charge** - Общая сумма оплаты за услуги вечером, количественный признак  \n",
    "**Total night minutes** - Общая длительность разговоров ночью, количественный признак  \n",
    "**Total night calls** - Общее количество звонков ночью, количественный признак  \n",
    "**Total night charge** - Общая сумма оплаты за услуги ночью, количественный признак  \n",
    "**Total intl minutes** - Общая длительность международных разговоров, количественный признак  \n",
    "**Total intl calls** - Общее количество  международных разговоров, количественный признак  \n",
    "**Total intl charge** -  Общая сумма оплаты за международные разговоры, количественный   признак  \n",
    "**Customer service calls** - Количество обращений в сервисный центр, количественный признак \n",
    "  \n",
    "Целевая переменная: **Churn** - Признак оттока, бинарный признак (1 - потеря клиента, то есть отток)  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <center>Описание предобработки данных</center>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Тип данных признаков 'International plan' и 'Voice mail plan' - объекты,\n",
    "# нужно преобразовать  в булевый тип.\n",
    "obj_cols = ['International plan', 'Voice mail plan']\n",
    "df[obj_cols] = df[obj_cols] == 'Yes'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Преобразование номинального признака названия штата в количественный\n",
    "state_encoder = preprocessing.LabelEncoder()\n",
    "state_encoder.fit(df['State'])\n",
    "df['State'] = state_encoder.transform(df['State']).astype(\"float64\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  <center>Первичный анализ признаков</center>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Просмотр типов данных\n",
    "df.info()\n",
    "\n",
    "# Все данные заполнены, пропусков нет."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Смотрим на статистические характеристики:\n",
    "df.describe().T"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Распределение целевой переменной\n",
    "df['Churn'].hist()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['Churn'].value_counts()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Выборка не сбалансированна, одного класса больше чем другого."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <center>Первичный визуальный анализ признаков </center>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Анализируемые признаки (переменная создана для удобства предварительного анализа)\n",
    "predictors = [\n",
    "#  'State',\n",
    " 'Account length',\n",
    "#  'Area code',\n",
    "# 'International plan',\n",
    "# 'Voice mail plan',\n",
    " 'Number vmail messages',\n",
    " 'Total day minutes',\n",
    " 'Total day calls',\n",
    " 'Total day charge',\n",
    " 'Total eve minutes',\n",
    " 'Total eve calls',\n",
    " 'Total eve charge',\n",
    " 'Total night minutes',\n",
    " 'Total night calls',\n",
    " 'Total night charge',\n",
    " 'Total intl minutes',\n",
    " 'Total intl calls',\n",
    " 'Total intl charge',\n",
    " 'Customer service calls',\n",
    "#  'Churn'\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Ищем коррелирующие признаки\n",
    "corr = df[predictors].corr()\n",
    "sns.heatmap(corr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Строим графики распределения признаков\n",
    "plots = df[predictors].hist(figsize=(12,10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <center>Закономерности:</center>\n",
    "1. На первый взгляд величина среднего количества звонков в разное время суток наблюдается на одном уровне, средняя продолжительность звонков вечером и ночью в среднем все-таки больше. Это соответствует действительности и логично, поскольку люди чаще совершают продолжительные звонки в свободное от работы и учебы время, а днем большинство совершаются кратковременные деловые звонки.\n",
    "2. Несмотря на то что средняя продолжительность разговоров увеличивается по мере смены времени суток (вечером и ночью больше, чем днем), мы можем наблюдать, что среднее количество звонков остается на прежнем уровне. Но при этом сумма оплаты разговоров снижается, что, вероятно, связанно с повременным тарифом, т.е. в разное время суток, разная тарификация.\n",
    "3. Средняя продолжительность международных звонков небольшая. Это связанно с дорогими тарифами на роуминг."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пробуем найти влияние количества звонков, общей продолжительности и суммы оплаты.\n",
    "# Берем вечерний период, так как он самый активный. \n",
    "sns.pairplot(df[['Total eve minutes', 'Total eve calls', 'Total eve charge',\n",
    "                 'Churn']], hue='Churn')\n",
    "\n",
    "# Зависимости не выявлено, отток клиентов по этим параметрам равномерен.\n",
    "# Наблюдается линейная зависимость суммы оплаты от общей продолжительности разговоров."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Основываясь на экспертном мнении, можно предположить, что на отток клиента может сильно влиять общее время обслуживания клиента и количество обращений в сервисный центр. Пробуем выявить зависимость."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Зависимость признака оттока от количества обращений в сервисный центр\n",
    "sns.countplot(x='Customer service calls', hue=\"Churn\", data=df)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Подсчитаем долю вышедших в отток клиентов от не вышедших и\n",
    "долю вышедших в отток от суммы всех клиентов в определенном количестве звонков.\n",
    "\n",
    "Построим линейные графики."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "true_service_calls = df[df['Churn'] == True].groupby('Customer service calls')['Churn'].count()\n",
    "false_service_calls = df[df['Churn'] == False].groupby('Customer service calls')['Churn'].count()\n",
    "ratio = true_service_calls / false_service_calls * 100\n",
    "ratio_all = true_service_calls / (true_service_calls + false_service_calls) * 100"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.plot(ratio)\n",
    "plt.plot(ratio_all)\n",
    "plt.xlabel('Customer service calls')\n",
    "plt.ylabel('Churn')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Так как продолжительность является количественным и непрерывным признаком,\n",
    "# то для упрощения визуализации разделим клиентов на несколько 'поколений'.\n",
    "df['Generation'] = df['Account length'].apply(lambda x: x//30)\n",
    "sns.countplot(x='Generation', hue=\"Churn\", data=df)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Подсчитаем доли вышедших в отток клиентов от не вышедших и\n",
    "долю вышедших в отток от суммы всех клиентов по поколениям.\n",
    "\n",
    "Построим линейные графики."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "true_generation = df[df['Churn'] == True].groupby('Generation')['Churn'].count()\n",
    "false_generation = df[df['Churn'] == False].groupby('Generation')['Churn'].count()\n",
    "ratio = true_generation / false_generation * 100\n",
    "ratio_all = true_generation / (true_generation + false_generation) * 100"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.plot(ratio)\n",
    "plt.plot(ratio_all)\n",
    "plt.xlabel('Generation')\n",
    "plt.ylabel('Churn')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <center>Инсайты:</center>\n",
    "1. Клиенты, совершающие более 3-х звонков в call-центр, имеют разительно  более высокий процент оттока, Это может быть обусловлено тем, что большое кол-во звонков объясняется существованием серьезных проблем у звонящих клиентов, что сильно повышает вероятность их попадания в отток.  \n",
    "В случаях с меньшим количеством звонков, чем 3, процент оттока остается стабильным на достаточно низком уровне (10%-15%), что подтверждает приведенную выше гипотезу.\n",
    "2. Анализ единиц продолжительности жизни клиента показал, что увеличение данного показателя ведет к более интенсивному оттоку клиентов. \n",
    "Данная закономерность ожидаема и может говорить о том, что рассматриваемый клиентский продукт подвержен изменениям \"моды\".  \n",
    "C шестого этапа жизни (из выделенных периодов в 30 единиц), заметно значительное увеличение интенсивности оттока.  \n",
    "Это может быть обусловлено тем, что на этот момент клиент приобретает определенное отрицательное благо, либо теряет положительное.  \n",
    "Например, теряет льготные условия обслуживания, которые предоставлялись ему в течение 6 этапов и т. п. \n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <center>Создание новых признаков</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Звонки по телефону - это целевая услуга, которая предоставляется абоненту,\n",
    "поэтому пробуем рассчитать, сколько стоит гипотетическая \"минута\" (то есть без учета других услуг) разговора у абонента."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['Cost of Minute'] = (df['Total day charge'] + \n",
    "                        df['Total eve charge'] +\n",
    "                        df['Total night charge'] +\n",
    "                        df['Total intl charge']) / (df['Total day minutes'] + \n",
    "                                                    df['Total eve minutes'] +\n",
    "                                                    df['Total night minutes'] +\n",
    "                                                    df['Total intl minutes'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Визуальный анализ нового признака\n",
    "df['round'] = df['Cost of Minute'].apply(lambda x: round(x, 2) * 100)\n",
    "sns.countplot(x='round', hue=\"Churn\", data=df)\n",
    "plt.xlabel('Cents in a minute')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "true_generation = df[df['Churn'] == True].groupby('round')['Churn'].count()\n",
    "false_generation = df[df['Churn'] == False].groupby('round')['Churn'].count()\n",
    "ratio = true_generation / false_generation * 100\n",
    "ratio_all = true_generation / (true_generation + false_generation) * 100"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.plot(ratio)\n",
    "plt.plot(ratio_all)\n",
    "plt.xlabel('Cents in a minute')\n",
    "plt.ylabel('Churn')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Анализ стоимости \"минуты\" показывает, что признак имеет важное значение.\n",
    "При стоимости минуты больше 10 центов, вероятность попадания абонента в отток возрастает,\n",
    "а при стоимости менее 7 центов за минуту - вероятность очень мала."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <center>Отбор признаков</center>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Маштабируем переменные и конвертируем назад в Pandas DataFrame\n",
    "df_scale = preprocessing.scale(df)\n",
    "df_scale = pd.DataFrame(df_scale)\n",
    "df_scale.columns = df.columns"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для оценки важности признаков сделаем предсказание случайного леса с параметрами по умолчанию. Вместо кросс-валидации будем использовать Out-of-Bag оценку."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Выделим обучающую выборку и целевую переменую \n",
    "X, y = df[[s for s in df_scale.columns if s != 'Churn']], df['Churn']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "first_forest = RandomForestClassifier(n_estimators=1000, max_depth = 5, \n",
    "                                      oob_score=True, n_jobs=-1,\n",
    "                                      random_state=42).fit(X, y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Посмотрим точность предсказания\n",
    "first_forest.oob_score_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "first_forest_predictions = first_forest.predict(X)\n",
    "features = pd.DataFrame(first_forest.feature_importances_, index=X.columns,\n",
    "                        columns=['Importance']).sort(['Importance'], ascending=False)\n",
    "features"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Кривая оценок важности признаков\n",
    "plt.plot(range(len(features.Importance.tolist())), \n",
    "         features.Importance.tolist())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Анализируя оценки важности можно сделать следующие выводы:\n",
    "1. Ожидаемо вспомогаемые признаки Generation и round имеют малый вес. Также ожидаемо, что код штата и префикс номера имеют малый вес. Эти признаки не будут использоваться в обучающей выборке.\n",
    "2. Показатели активности абонента с маленькими значениями, например: Account length, Total day calls и другие, все же могут вносить незначительные коррективы, поэтому удалятся не будут."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X = df[['Total day charge',\n",
    "        'Total day minutes',\n",
    "        'Customer service calls',\n",
    "        'International plan',\n",
    "        'Total eve minutes',\n",
    "        'Total eve charge',\n",
    "        'Cost of Minute',\n",
    "        'Number vmail messages',\n",
    "        'Total intl calls',\n",
    "        'Total intl minutes',\n",
    "        'Voice mail plan',\n",
    "        'Total intl charge',\n",
    "        'Total night minutes',\n",
    "        'Total night charge',\n",
    "        'Total day calls',\n",
    "        'Total night calls',\n",
    "        'Account length',\n",
    "        'Total eve calls']]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <center>Построение модели классификации</center>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Разбиваем на тестовую и обучающую выборку\n",
    "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Попробуем четыре разных классификатора: логистическую регрессию, метод ближайших соседей, Gradient boosting, Random Forest и SVM. Так как у нас сильный дисбаланс в выборке, то в качестве меры будем использовать F1 score."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "classifiers = [LogisticRegression(),\n",
    "               KNeighborsClassifier(),\n",
    "               GradientBoostingClassifier(), \n",
    "               RandomForestClassifier(), \n",
    "               SVC()]\n",
    "classifiers_name = ['LogisticRegression',\n",
    "                    'KNeighborsClassifier',\n",
    "                    'GradientBoostingClassifier', \n",
    "                    'RandomForestClassifier', \n",
    "                    'SVC']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Настройка параметров выбранных алгоритмов с помощью GridSearchCV \n",
    "n_folds = 5\n",
    "scores = []\n",
    "fits = []\n",
    "logistic_params = {'penalty': ('l1', 'l2'),\n",
    "                   'C': (.01,.1,1,5)}\n",
    "knn_params = {'n_neighbors': list(range(3, 12, 2))}\n",
    "gbm_params = {'n_estimators': [100, 300, 500],\n",
    "              'learning_rate':(0.1, 0.5, 1),\n",
    "              'max_depth': list(range(3, 6)), \n",
    "              'min_samples_leaf': list(range(10, 31, 10))}\n",
    "forest_params = {'n_estimators': [100, 300, 500],\n",
    "                 'criterion': ('gini', 'entropy'), \n",
    "                 'max_depth': list(range(3, 6)), \n",
    "                 'min_samples_leaf': list(range(10, 31, 10))}\n",
    "\n",
    "svm_param = {'kernel' : ('linear', 'rbf'), 'C': (.5, 1, 2)}\n",
    "params = [logistic_params, knn_params, gbm_params, forest_params, svm_param]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <center>Кросс-валидация</center>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for i, each_classifier in enumerate(classifiers):\n",
    "    clf = each_classifier\n",
    "    clf_params = params[i]\n",
    "    grid = GridSearchCV(clf, clf_params, \n",
    "                        cv=StratifiedKFold(y_train, n_folds=n_folds,\n",
    "                        shuffle=False, random_state=42), \n",
    "                        n_jobs=-1, scoring=\"f1\")\n",
    "    grid.fit(X_train, y_train)\n",
    "    fits.append(grid.best_params_)\n",
    "    clf_best_score = grid.best_score_\n",
    "    scores.append(clf_best_score)\n",
    "    print(classifiers_name[i], clf_best_score, \"\\n\", grid.best_params_, \"\\n\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid_value = max(scores)\n",
    "grid_index = [i for i in range(len(scores)) if scores[i]==grid_value][0]\n",
    "print(\"Лучший классификатор при GridSearch:\",\n",
    "      classifiers_name[grid_index], grid_value)\n",
    "print(fits[grid_index])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "clf_params = {'n_estimators': (300, 350, 400), \n",
    "              'learning_rate': (0.1, 0.3, 0.5, 0.75, 1), \n",
    "              'min_samples_leaf': list(range(1, 14, 3))}\n",
    "\n",
    "clf = classifiers[grid_index]\n",
    "grid = GridSearchCV(clf, clf_params, cv=n_folds, \n",
    "                    n_jobs=-1, scoring=\"f1\")\n",
    "grid.fit(X_train, y_train)\n",
    "clf_best_score = grid.best_score_\n",
    "clf_best_params = grid.best_params_\n",
    "clf_best = grid.best_estimator_\n",
    "mean_validation_scores = []\n",
    "print(\"Лучший результат\", clf_best_score, \n",
    "      \"лучшие параметры\", clf_best_params)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <center>Построение кривых валидации и обучения</center>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_with_std(x, data, **kwargs):\n",
    "        mu, std = data.mean(1), data.std(1)\n",
    "        lines = plt.plot(x, mu, '-', **kwargs)\n",
    "        plt.fill_between(x, mu - std, mu + std, edgecolor='none',\n",
    "                         facecolor=lines[0].get_color(), alpha=0.2)\n",
    "        \n",
    "def plot_learning_curve(clf, X, y, scoring, cv=5):\n",
    " \n",
    "    train_sizes = np.linspace(0.05, 1, 20)\n",
    "    n_train, val_train, val_test = learning_curve(clf,\n",
    "                                                  X, y, train_sizes, cv=cv,\n",
    "                                                  scoring=scoring)\n",
    "    plot_with_std(n_train, val_train, label='training scores', c='green')\n",
    "    plot_with_std(n_train, val_test, label='validation scores', c='red')\n",
    "    plt.xlabel('Training Set Size'); plt.ylabel(scoring)\n",
    "    plt.legend()\n",
    "\n",
    "def plot_validation_curve(clf, X, y, cv_param_name, \n",
    "                          cv_param_values, scoring):\n",
    "\n",
    "    val_train, val_test = validation_curve(clf, X, y, cv_param_name,\n",
    "                                           cv_param_values, cv=5,\n",
    "                                                  scoring=scoring)\n",
    "    plot_with_std(cv_param_values, val_train, \n",
    "                  label='training scores', c='green')\n",
    "    plot_with_std(cv_param_values, val_test, \n",
    "                  label='validation scores', c='red')\n",
    "    plt.xlabel(cv_param_name); plt.ylabel(scoring)\n",
    "    plt.legend()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Кривая обучения\n",
    "plot_learning_curve(GradientBoostingClassifier(n_estimators=2, \n",
    "                    learning_rate=1.5, min_samples_leaf=7),\n",
    "                   X_train, y_train, scoring='f1', cv=10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Кривая валидации\n",
    "learning_rates = np.linspace(0.1, 2.3, 20)\n",
    "plot_validation_curve(GradientBoostingClassifier(n_estimators=250, \n",
    "                    min_samples_leaf=7), X_train, y_train, \n",
    "                    cv_param_name='learning_rate', \n",
    "                    cv_param_values=learning_rates,\n",
    "                    scoring='f1')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <center>Финальный прогноз для отложенной выборки</center>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "final_gbm = GradientBoostingClassifier(n_estimators=300, \n",
    "                    min_samples_leaf=10, learning_rate=0.1, max_depth=4)\n",
    "final_gbm.fit(X_train, y_train)\n",
    "final_pred = final_gbm.predict(X_test)\n",
    "accuracy_score(y_test, final_pred), f1_score(y_test, final_pred)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <center>Оценка модели с описанием выбранной метрики</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Построена модель предсказания, уйдет ли абонент телеком-копмании в отток. Модель предсказывает с 96%-ной долей правильных ответов на отложенных 30% выборки. Но accuracy не очень хорошо характеризует качество модели из-за сильного дисбаланса в целевой переменной (~85% против ~15%), поэтому в качестве целевой была выбрана метрика F1-score. На отложенной выборке удалось добиться хорошего результата F1=0.82. Построены кривые обучения и валидационные кривые. Видно, что увеличение количества примеров более 1700 не приносит существенной выгоды (у нас в обучающей выборке более 3000 примеров)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## <center>Общие выводы</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "По результатам проведенного анализа, можно увидеть, что есть определенные зависимости и признаки показателя оттока. \n",
    "Наблюдаются определенные закономерности - продолжительность звонков в определенное время суток, международные звонки, сумма оплаты разговоров. \n",
    "Имеет влияние количество обращений клиентами в сервисный центр - совершающие более 3-х звонков с большей вероятностью попадут в отток. \n",
    "Также прямое отношение в показателю оттока имеет срок жизни клиента - клиенты со сроком жизни более 6-ти отрезков времени по 30 единиц имеют больший риск попасть в отток. \n",
    "Выявленным признаком оттока является стоимость минуты разговора клиента - минимальная вероятность попадания клиента в отток наблюдается при стоимости минуты разговора клиента при 7 центах. С наибольшей вероятностью клиент покинет компанию при стоимости в 10 центов и более."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
